import { IApiControl, IRequestConfig, IResponse, IResolve, IRejectType, IRequestHook } from './BaseInterface'
import ApiCount, { IApiCount } from './ApiMap/ApiCount'
import ApiCache, { IApiCache } from './ApiMap/ApiCache'
import { errTips } from './Utils'

/**
 * Base 请求类
 */
export default class BaseRequest {
    // 请求统计
    apiCount: ApiCount
    // 请求缓存
    apiCache: ApiCache
    // 默认配置参数
    options: IRequestConfig;
    // 默认流程控制参数
    apiControl: IApiControl;
    // 默认请求函数
    request: (options: IRequestConfig) => Promise<any>;
    constructor(options: IRequestConfig, apiControl: IApiControl) {
        this.apiCount = new ApiCount()
        this.apiCache = new ApiCache()
        this.options = options
        this.apiControl = apiControl
        this.request = (options: IRequestConfig) => Promise.resolve(options)
    }
    /**
     * 请求入口函数
     * @param options 请求参数
     * @returns 
     */

    apiMain(options: IRequestConfig, apiControl?: IApiControl): Promise<any> {
        this.apiCount.readyApiCount(options.url)
        // 整合请求参数
        options = Object.assign({}, this.options, options)
        // 整合请求流程控制配置
        apiControl = Object.assign({}, this.apiControl, apiControl)
        return this.requestWrapper(options, apiControl)
    }
    /**
     * 包装请求
     * @param options 请求参数
     */
    requestWrapper(options: IRequestConfig, apiControl: IApiControl): Promise<any> {
        const { url } = options

        // 读取请求缓存
        const urlHash = this.apiCache.packKey(url, options)
        if (apiControl.resCache && !this.apiCache.valiOverdue(urlHash, apiControl.chacheExpires)) {
            let cacheData = this.apiCache.getData(urlHash)
            let result = JSON.parse(cacheData.data)
            return Promise.resolve(result)
        }
        // 根据 debounce 配置拦截重复请求(请求防抖)
        if (apiControl.debounce && this.apiCount.getApiState(url) == 'pending') {
            apiControl.debounceFn && apiControl.debounceFn()
            return new Promise(() => { }) // 空的 promise 不执行任何回调
        }
        // 根据请求前置逻辑判断是否发起请求
        const { type, message } = this.requestHook(options, apiControl)
        if (!type) {
            this.apiCount.setApiState(url, 'reject')
            const info: IRejectType = { type: 'fail', position: 'request', message }
            errTips(apiControl, info)
            return Promise.reject(info)
        }
        this.apiCount.setApiState(url, 'pending')
        // 类似 axios 的返回前置处理，axios 那里是数组这里用一个就够了， 后面有需要再加吧^_^
        return this.request(options).then(
            response => this.promiseResolveBefore(response, options, apiControl)
            ,
            error => this.promiseRejectBefore(error, options, apiControl)
        )
    }
    /**
     * 请求前置钩子
     * @param options 请求参数
     * @param apiControl 请求控制参数
     * @returns true 发起请求，false 不请求
     */
    requestHook(options: IRequestConfig, apiControl: IApiControl): IRequestHook {
        if (apiControl.interceptors && apiControl.interceptors.request) {
            return apiControl.interceptors.request(options, apiControl)
        }
        return { type: true, message: 'ok' }
    }
    /**
     * 返回前置钩子
     * @param options 请求阐述
     * @param response 返回体
     * @param promiseFn promise处理函数
     */
    responseHook(options: IRequestConfig, apiControl: IApiControl, response: IResponse): Promise<any> | IResolve {
        if (apiControl.interceptors && apiControl.interceptors.response) {
            return apiControl.interceptors.response(response, options, apiControl)
        } else {
            return response
        }
    }
    /**
     * 成功的返回 前置处理
     * @param response 返回体
     * @param options 接口配置
     * @param apiControl 接口控制参数
     */
    promiseResolveBefore(response: IResponse, options: IRequestConfig, apiControl: IApiControl): Promise<any> {
        this.finally(options, apiControl)
        const { url } = options
        // 全局日志打印
        if (apiControl.reslog) {
            console.log(response, `[ ${url} ]`)
        }
        let resData: IResolve | Promise<any>;
        try {
            // 尝试执行 自定义拦截器 若执行异常 或返回 reject 则进行错误处理逻辑
            resData = this.responseHook(options, apiControl, response)
        } catch (error) {
            console.info('responseHook error')
            return this.promiseRejectBefore(error, options, apiControl)
        }
        this.apiCount.setApiState(url, 'reslove')
        if (resData instanceof Promise) {
            return resData.then(res => {
                this.promiseresolveCache(options, apiControl, res)
                return Promise.resolve(res)
            }).catch(err => this.promiseRejectBefore(err, options, apiControl))
        } else {
            this.promiseresolveCache(options, apiControl, resData)
            return Promise.resolve(resData.data)
        }
    }
    /**
     * 成功的返回 缓存处理
     * @param options 接口配置
     * @param apiControl 接口控制参数
     * @param resData 接口返回数据
     */
    promiseresolveCache(options: IRequestConfig, apiControl: IApiControl, resData: IResolve) {
        const { url } = options
        // 缓存结果
        const urlHash = this.apiCache.packKey(url, options)
        if (apiControl.resCache && !this.apiCache.hasData(urlHash)) {
            // 保存字符串每次返回一个新的对象防止页面对数据误操作
            this.apiCache.setData(urlHash, {
                data: JSON.stringify(resData.data),
                createdTime: Date.now()
            })
            apiControl.extraCache && apiControl.extraCache(url, resData.data)
        }
    }
    /**
     * 失败的返回 前置处理
     * @param error 失败的结果
     * @param options 接口配置
     * @param apiControl 接口控制参数
     */
    promiseRejectBefore(error: any, options: IRequestConfig, apiControl: IApiControl): Promise<any> {
        this.finally(options, apiControl)
        const { url } = options
        this.apiCount.setApiState(url, 'reject')
        const errorInfo: IRejectType = { type: 'error', position: 'response', message: error }
        errTips(apiControl, errorInfo)
        return Promise.reject(errorInfo)
    }
    /**
     * 请求完成后的回调
     * @param options 接口配置
     * @param apiControl 接口控制参数
     */
    finally(options: IRequestConfig, apiControl: IApiControl): void {
        if (apiControl.finally) {
            apiControl.finally(options)
        }
    }
    /**
     * 获取请求统计数据
     * @returns 统计列表
     */
    getReqCount(): Map<string, IApiCount> {
        return this.apiCount.mapList
    }
    /**
     * 获取请求缓存数据
     * @returns 缓存列表
     */
    getReqCache(): Map<string, IApiCache> {
        return this.apiCache.mapList
    }
}